/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       scene.cpp
 * Author:     karooolek
 * Created on: 2009-03-10
 *
 **********************************************************************************************************************/

#include "scene.h"

#undef _DEBUG

#ifdef _DEBUG
#include <GL/glut.h>
#endif

namespace mGameEngine
{
namespace Game
{

using namespace Graphics;
using namespace Audio;
using namespace Physics;

NxScene *_createPhysxScene() 
{
    NxPhysicsSDK *physx = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);
    NxSceneDesc desc;
    desc.flags |= NX_SF_ENABLE_ACTIVETRANSFORMS;
    return physx->createScene(desc);
}

Scene::Scene() :
    _timer(),
    _bgcolor(),
    _soundSpeed(340.0f), _doppler(1.0f),
    _gravity(0.0f, 0.0f, -9.81f),
    _scene(_createPhysxScene()),
    _root(NULL), _active(NULL)
{
    _createPhysxScene();
}

Scene::Scene(SceneNode *root, SceneNode *active) :
    _timer(),
    _bgcolor(),
    _soundSpeed(340.0f), _doppler(1.0f),
    _gravity(0.0f, 0.0f, -9.81f),
    _scene(_createPhysxScene())
{
    setRootSceneNode(root);
    setActiveNode(active);
}

Scene::~Scene()
{
    // delete PhysX scene
    if(_scene)
    {
        NxPhysicsSDK *physx = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);
        physx->releaseScene(*_scene);
    }

    // delete scene nodes
    if(_root)
    {
        delete _root;
    }
}

SceneNode *Scene::getRootSceneNode() const
{
    return _root;
}

void Scene::setRootSceneNode(SceneNode *root)
{
    // same scene node
    if(root == _root)
    {
        return;
    }

    _root = root;

    // set scene and parent
    _root->_setScene(this);
    _root->_parent = NULL;
}

const Camera *Scene::_getActiveCamera() const
{
    // no active node, or node without camera
    if(!_active || !_active->getCamera())
    {
        static PerspectiveCamera defCam;
        return &defCam;
    }

    // active node camera
    return _active->getCamera();
}

const Microphone *Scene::_getActiveMicrophone() const
{
    // no active node or node without microphone
    if(!_active || !_active->getMicrophone())
    {
        static Microphone defMic;
        return &defMic;
    }

    // active node microphone
    return _active->getMicrophone();
}

uint8 Scene::_setLights(const SceneNode *node, uint8 l) const
{
    // max number of lights exceeded
    if(l > 7)
    {
        return l;
    }

    glPushMatrix();

        // load transformation
        glMultMatrixf(node->getTransformation());

        // set lights
        const List<Light *> &lights = node->getLights();
        for(List<Light *>::ConstIterator i = lights.begin();
            i != lights.end() && l <= 7;
            ++i)
        {
            const Light *light = *i;
            if(light->isEnabled())
            {
                glEnable(GL_LIGHT0 + l);
                light->set(l);
                ++l;
            }
        }

        // set subnodes lights
        const List<SceneNode *> &nodes = node->getSceneNodes();
        for(List<SceneNode *>::ConstIterator i = nodes.begin();
            i != nodes.end();
            ++i)
        {
            const SceneNode *node = *i;

            if(node->getSceneNodes().size() || node->getLights().size())
            {
                l = _setLights(node, l);
            }
        }

    glPopMatrix();

    // disable inactive lights
    if(node == _root)
    {
        for(; l <= 7; ++l)
        {
            glLightfv(GL_LIGHT0 + l, GL_AMBIENT, Color());
            glLightfv(GL_LIGHT0 + l, GL_DIFFUSE, Color());
            glLightfv(GL_LIGHT0 + l, GL_SPECULAR, Color());
        }
    }

    return l;
}

void Scene::_drawNodes(const SceneNode *node) const
{
    glPushMatrix();

        // load transformation
        glMultMatrixf(node->getTransformation());

        // get current modelview matrix
        Matrix4x4 modelview;
        glGetFloatv(GL_MODELVIEW_MATRIX, modelview);

        // get active camera
        const Camera *cam = _getActiveCamera();

        // draw drawables
        const List<Drawable *> &drawables = node->getDrawables();
        for(List<Drawable *>::ConstIterator i = drawables.begin(); i != drawables.end(); ++i)
        {
            Drawable *drawable = *i;

            // drawable is invisible
            if(!drawable->isVisible())
            {
                continue;
            }

            // get transformed bounding sphere
            Sphere sphere = drawable->getBoundingSphere();
            sphere.setCenter(modelview * sphere.getCenter());

            // frustum culling
            if(!cam->isInCamera(sphere))
            {
                continue;
            }

            // calculate LOD reduction
            float projSize = cam->getProjectedSize(sphere);

            int viewport[4];
            glGetIntegerv(GL_VIEWPORT, viewport);
            float pix = float(viewport[3]);

            float lod = 1.5f * max(0.0f, log2(512.0f / projSize / pix));

            // draw drawable
            drawable->setLevelOfDetail(lod);
            drawable->draw();

#if _DEBUG
            //draw bounding sphere
            GPUProgram::useFixedPipeline();
            
            glDisable(GL_BLEND);
            glDisable(GL_TEXTURE_1D);
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_TEXTURE_3D);
            glActiveTexture(GL_TEXTURE0);
            glDisable(GL_TEXTURE_CUBE_MAP);
            glDisable(GL_LIGHTING);

            glPushMatrix();
                Vector4 c = drawable->getBoundingSphere().getCenter();
                float r = drawable->getBoundingSphere().getRadius();
                glTranslatef(c.x, c.y, c.z);
                glColor4fv(Color(1.0f, 1.0f, 1.0f, 1.0f));
                glutWireSphere(r, 8, 8);
            glPopMatrix();
            
            glEnable(GL_BLEND);
            glEnable(GL_TEXTURE_1D);
            glEnable(GL_TEXTURE_2D);
            glEnable(GL_TEXTURE_3D);
            glEnable(GL_TEXTURE_CUBE_MAP);
            glEnable(GL_LIGHTING);
#endif
        }

        // draw subnodes
        const List<SceneNode *> &nodes = node->getSceneNodes();
        for(List<SceneNode *>::ConstIterator i = nodes.begin(); i != nodes.end(); ++i)
        {
            const SceneNode *node = *i;

            if(node->getSceneNodes().size() || node->getDrawables().size())
            {
                _drawNodes(node);
            }
        }

#if _DEBUG
        // draw node axes for debug
        GPUProgram::useFixedPipeline();
        
        glDisable(GL_BLEND);
        glDisable(GL_TEXTURE_1D);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_TEXTURE_3D);
        glActiveTexture(GL_TEXTURE0);
        glDisable(GL_TEXTURE_CUBE_MAP);
        glDisable(GL_LIGHTING);

        glBegin(GL_LINES);

            glColor4fv(Color(1.0f, 1.0f, 1.0f, 1.0f));
            for(List<SceneNode *>::ConstIterator i = nodes.begin(); i != nodes.end(); ++i)
            {
                SceneNode *node = *i;
                glVertex3fv(Position(0.0f, 0.0f, 0.0f));
                glVertex3fv(node->getPosition());
            }

            glColor4fv(Color(1.0f, 0.0f, 0.0f, 1.0));
            glVertex3fv(Position(0.0f, 0.0f, 0.0f));
            glVertex3fv(Position(1.0f, 0.0f, 0.0f));

            glColor4fv(Color(0.0f, 1.0f, 0.0f, 1.0f));
            glVertex3fv(Position(0.0f, 0.0f, 0.0f));
            glVertex3fv(Position(0.0f, 1.0f, 0.0f));

            glColor4fv(Color(0.0f, 0.0f, 1.0f, 1.0f));
            glVertex3fv(Position(0.0f, 0.0f, 0.0f));
            glVertex3fv(Position(0.0f, 0.0f, 1.0f));
        glEnd();

        glEnable(GL_BLEND);
        glEnable(GL_TEXTURE_1D);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_3D);
        glEnable(GL_TEXTURE_CUBE_MAP);
        glEnable(GL_LIGHTING);
#endif

    glPopMatrix();
}

void Scene::_setSoundSources(const SceneNode *node) const
{
    glPushMatrix();

        // load transformation
        glMultMatrixf(node->getTransformation());

        // set sound sources
        const List<SoundSource *> &soundSources = node->getSoundSources();
        for(List<SoundSource *>::ConstIterator i = soundSources.begin(); i != soundSources.end(); ++i)
        {
            const SoundSource *soundSource = *i;

            if(soundSource->isPlaying())
            {
                soundSource->set();
            }
        }

        // set subnodes sound sources
        const List<SceneNode *> &nodes = node->getSceneNodes();
        for(List<SceneNode *>::ConstIterator i = nodes.begin(); i != nodes.end(); ++i)
        {
            const SceneNode *node = *i;

            if(node->getSceneNodes().size() || node->getSoundSources().size())
            {
                _setSoundSources(node);
            }
        }

    glPopMatrix();
}

void Scene::_createActors(SceneNode *node)
{
    // create actor
    Actor *actor = node->getActor();
    if(actor && !actor->isCreated())
    {
        actor->create(_scene);
        actor->setConnectedObject(node);
        actor->setTransformation(node->getTransformation(_root));

        // create non existing joints
        const List<Joint *> &joints = actor->getJoints();
        for(List<Joint *>::ConstIterator i = joints.begin(); i != joints.end(); ++i)
        {
            Joint *joint = *i;

            Actor *a1 = joint->getFirstActor();
            a1->create(_scene);
            Actor *a2 = joint->getSecondActor();
            a2->create(_scene);

            joint->create(a1->_createJointActor(joint), a2->_createJointActor(joint));
        }
    }

    // connect actor to object
    if(actor && actor->isCreated() && actor->getConnectedObject() != node)
    {
        actor->setConnectedObject(node);
    }

    // create actors for subnodes
    const List<SceneNode *> &nodes = node->getSceneNodes();
    for(List<SceneNode *>::ConstIterator i = nodes.begin(); i != nodes.end(); ++i)
    {
        SceneNode *node = *i;

        _createActors(node);
    }
}

void Scene::idle()
{
    // no root node
    if(!_root)
    {
        return;
    }

    // create non existing actors
    _createActors(_root);

    // set updating frequency
    static const float TIME_STEP = 0.01666666667f; // = 1/60
    float time = _timer.getTime();
    if(time >= TIME_STEP)
    {
        // fetch physics
        _scene->fetchResults(NX_ALL_FINISHED, true, NULL);
        
        // update active rigid bodies
        uint nActive;
        NxActiveTransform *active = _scene->getActiveTransforms(nActive);
        for(uint i = 0; i != nActive; ++i)
        {
            NxActiveTransform &a = active[i];

            if(!a.userData)
            {
                continue;
            }

            Matrix4x4 t;
            a.actor2World.getColumnMajor44(t);
            SceneNode *node = (SceneNode *)(a.userData);
            node->setTransformation(t, _root);
        }

        // update scene
        //for(float t = time; t >= TIME_STEP; t -= TIME_STEP)
        //{
            this->update(TIME_STEP);
        //}
    
        // simulate physics
        _scene->setGravity(NxVec3(_gravity));
        _scene->flushStream();
        _scene->simulate(time);

        // reset timer
        _timer.reset();
    }

    // render sound scene
    // set modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    Matrix4x4 transf = _active ? _active->getTransformation(_root) : Matrix4x4(1.0f);
    Vector3 pos = Vector3(transf.m[3]);
    Vector3 at = Vector3(transf.m[0]) + pos;
    Vector3 up = Vector3(transf.m[2]);

    gluLookAt(pos.x, pos.y, pos.z,
            at.x, at.y, at.z,
            up.x, up.y, up.z);

    // speed of sound
    alSpeedOfSound(_soundSpeed);

    // doppler factor
    alDopplerFactor(_doppler);

    // use microphone
    const Microphone *microphone = _getActiveMicrophone();
    microphone->use();

    // set sounds
    _setSoundSources(_root);

}

void Scene::update(float dt)
{
}

void Scene::draw() const
{
    // clear frame buffer
    glClearColor(_bgcolor.r, _bgcolor.g, _bgcolor.b, _bgcolor.a);
    glClearDepth(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // no root node to draw
    if(!_root)
    {
        return;
    }

    // depth testing
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    // backface culling
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    // alpha blending
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // texturing
    glEnable(GL_TEXTURE_1D);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_3D);
    glEnable(GL_TEXTURE_CUBE_MAP);

    // lighting
    glEnable(GL_LIGHTING);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, Color());

//    // fog
//    glEnable(GL_FOG);
//    glFogfv(GL_FOG_COLOR, _bgcolor);
//    glFogi(GL_FOG_MODE, GL_LINEAR);
//    glFogf(GL_FOG_START, 30.0f);
//    glFogf(GL_FOG_END, 60.0f);

    // use camera
    const Camera *camera = _getActiveCamera();
    camera->use();

    // set modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    Matrix4x4 transf = _active ? _active->getTransformation(_root) : Matrix4x4(1.0f);
    Vector3 pos = Vector3(transf.m[3]);
    Vector3 at = Vector3(transf.m[0]) + pos;
    Vector3 up = Vector3(transf.m[2]);

    gluLookAt(pos.x, pos.y, pos.z, at.x, at.y, at.z, up.x, up.y, up.z);

    // set lights
    _setLights(_root);

    // draw nodes
    _drawNodes(_root);

    // draw world axes for debug
#ifdef _DEBUG
    GPUProgram::useFixedPipeline();

    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_1D);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_TEXTURE_3D);
    glActiveTexture(GL_TEXTURE0);
    glDisable(GL_TEXTURE_CUBE_MAP);
    glDisable(GL_LIGHTING);

    glBegin(GL_LINES);
        glColor4fv(Color(1.0f, 0.0f, 0.0f, 1.0f));
        glVertex3fv(Vector3(0.0f, 0.0f, 0.0f));
        glVertex4fv(Vector4(1.0f, 0.0f, 0.0f, 0.0f));

        glColor4fv(Color(0.0f, 1.0f, 0.0f, 1.0f));
        glVertex3fv(Vector3(0.0f, 0.0f, 0.0f));
        glVertex4fv(Vector4(0.0f, 1.0f, 0.0f, 0.0f));

        glColor4fv(Color(0.0f, 0.0f, 1.0f, 1.0f));
        glVertex3fv(Vector3(0.0f, 0.0f, 0.0f));
        glVertex4fv(Vector4(0.0f, 0.0f, 1.0f, 0.0f));
    glEnd();
    
    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_1D);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_3D);
    glEnable(GL_TEXTURE_CUBE_MAP);
    glEnable(GL_LIGHTING);
#endif

}

}
}

